/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package erpretailx.Manager;

import erpretailx.Modul.Modul;
import erpretailx.Modul.ModulClassLoader;
import erpretailx.Modul.ModulPublic;
import erpretailx.Retail;
import erpretailx.util.URLFileSystem;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.StringTokenizer;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;

import java.util.zip.ZipFile;
import javax.swing.SwingUtilities;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;


/**
 *
 * @author Al Quthsi
 */
public class ModulManager {

    private final List<Modul> moduls = new ArrayList<Modul>();
    private static ModulManager singleton;
    private final List<ModulPublic> ModulPublics = new CopyOnWriteArrayList<ModulPublic>();


    //root of modul directory
    public static File MODUL_DIRECTORY = new File(Retail.getBIN_DIRECTORY().getParent(),"moduls").getAbsoluteFile();
    private ModulClassLoader classLoader;

    //get instance
    public static ModulManager getInstance(){
        //if singleton is null create
        if(singleton == null){
            singleton = new ModulManager();
            return singleton;
        }
        //return instance of modulmanager
        return singleton;
    }

    private ModulManager(){
        try{
            //get Modul directory

            MODUL_DIRECTORY = new File(Retail.getBIN_DIRECTORY().getParentFile(),"moduls").getCanonicalFile();
        }catch(IOException e){
            e.printStackTrace();
        }

        //check modul was installed if null move moduls
        if(System.getProperty("modul") == null){
            try {
                moveModuls();
            } catch (MalformedURLException ex) {
                Logger.getLogger(ModulManager.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        //if modul directory not exits create directory
        if(!MODUL_DIRECTORY.exists()){
            MODUL_DIRECTORY.mkdir();
        }
    }


    //this function to load all modul on file xml in file jar application
    private void loadInternalModuls(InputStreamReader reader) {
        SAXReader saxReader = new SAXReader();
        Document modulXML = null;

        try{
            modulXML = saxReader.read(reader);
        }catch(DocumentException e){
            e.printStackTrace();
        }

        List Moduls = modulXML.selectNodes("/moduls/modul");
        for(Object modul1 : Moduls){
            String clazz = null;
            String name;

            try{
                //assign modul with modul1
                Element modul = (Element) modul1;

                clazz = modul.selectSingleNode("class").getText();
                name = modul.selectSingleNode("name").getText();
                
                Modul modulClass = (Modul) Class.forName(clazz).newInstance();
                registerModul(modulClass);
            }catch(Throwable c){
                c.printStackTrace();
            }


        }
    }

    //this function to load public modul from direktori
    private Modul loadPublicModul(File modDir) {
        File modulFile = new File(modDir,"modul.xml");
        SAXReader saxReader = new SAXReader();
        Document modulXML = null;
        try{
            modulXML = saxReader.read(modulFile);
        }catch(DocumentException e){
            e.printStackTrace();
        }

        //variabel for return
        Modul modulClass = null;

        //moduls a variable for store plugin element
        List moduls = modulXML.selectNodes("/plugin");
        for(Object tempModul : moduls){
            ModulPublic modulPublic = new ModulPublic();

            String clazz = null;
            String name;
            String minVer;

            //get element plugin from xml
            try{
                Element modul = (Element)tempModul;
                name = modul.selectSingleNode("name").getText();
                clazz = modul.selectSingleNode("class").getText();

            }catch(Exception c){
                c.printStackTrace();
                return null;
            }

            //set value of element xml to class modulPublic
            modulPublic.setName(name);
            modulPublic.setModulClass(clazz);

            
            try{
            modulClass = (Modul) Class.forName(clazz).newInstance();
            modulPublic.setModuldir(modDir);
            ModulPublics.add(modulPublic);
            
            registerModul(modulClass);

            }catch(Exception c){
                c.printStackTrace();
            }
        }
        return modulClass;
    }

    private void loadPublicModuls() {
            // First, expand all modul.
        expandNewModuls();


        File[] files = MODUL_DIRECTORY.listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return dir.isDirectory();
            }
        });

        // Do nothing if no jar or zip files were found
        if (files == null) {
            return;
        }

        for (File file : files) {
            File modulXML = new File(file, "modul.xml");
            if (modulXML.exists()) {
                try {
                    classLoader.addModul(file);
                    loadPublicModul(file);
                }
                catch (Throwable e) {
                    e.printStackTrace();
                }
            }
        }
    }


    private void moveModuls() throws MalformedURLException {
        File newModul = new File("","moduls").getAbsoluteFile();
        newModul.mkdir();

        File[] files = MODUL_DIRECTORY.listFiles();
        //if list file not empty
        if(files != null){
            //count length file in directory modul
            final int no = files.length;
            for(int i=0;i<no;i++){
                File file = files[i];
                if(file.isFile()){
                    //copy
                    File newFile = new File(newModul,file.getName());
                    if(newFile.lastModified() > file.lastModified()){
                        continue;
                    }
                    //copy proccess
                    try{
                        URLFileSystem.copy(file.toURI().toURL(), newFile);
                    }catch(IOException e){
                        e.printStackTrace();
                    }
                }
            }

        }
        MODUL_DIRECTORY = newModul;
    }

    //load all data form xml file
    public void loadModuls(){
        //delete all old moduls
        File[] oldFiles = MODUL_DIRECTORY.listFiles();
        if(oldFiles !=null){
            //for each file in oldFiles
            for(File file : oldFiles){
                if(file.isDirectory()){
                    File jarFile = new File(MODUL_DIRECTORY,file.getName() +".jar");
                    if(!jarFile.exists()){
                        uninstall(file);
                    }
                }
                
            }
        }

        updateClasspath();
        //load internal modul
        final URL url = getClass().getClassLoader().getResource("META-INF/moduls.xml");
        try {
            InputStreamReader reader = new InputStreamReader(url.openStream());
            loadInternalModuls(reader);
        }
        catch (IOException e) {
            e.printStackTrace();
        }

        //load public modul
        loadPublicModuls();

        String modul = System.getProperty("modul");
        if (modul != null) {
            final StringTokenizer st = new StringTokenizer(modul, ",", false);
            while (st.hasMoreTokens()) {
                String token = st.nextToken();
                File modulXML = new File(token);
                loadPublicModul(modulXML.getParentFile());
            }
        }

    }

    private void registerModul(Modul modulsClass) {
        moduls.add(modulsClass);
    }

    private void removeModul(Modul modulClass){
        moduls.remove(modulClass);
    }
    
    public Collection<Modul> getModuls(){
        return moduls;
    }

    private void uninstall(File modDir) {
        File[] files = modDir.listFiles();
        //delete each file from directory
        for(File delFile : files){
            if(delFile.isFile()){
                delFile.delete();
            }
        }

        File libDir = new File(modDir,"lib");

        File[] libs = libDir.listFiles();
        //delete each file in lib directory
        for(File lib: libs){
            if(lib.isFile()){
                lib.delete();
            }
        }

        //delete directory
        libDir.delete();
        modDir.delete();
    }
    
    public List<ModulPublic> getModulPublics(){
        return ModulPublics;
    }

    public void removePublicModul(ModulPublic modulPublic){
        for(ModulPublic mp : getModulPublics()){
            if(modulPublic.getName().equals(mp.getName())){
            ModulPublics.remove(mp);
        }
    }
    }

    private void unzipModul(File jar, File dir) {
        try {
            ZipFile zipFile = new JarFile(jar);
            // Ensure that this JAR is a plugin.
            if (zipFile.getEntry("plugin.xml") == null) {
                return;
            }
            dir.mkdir();
            for (Enumeration e = zipFile.entries(); e.hasMoreElements();) {
                JarEntry entry = (JarEntry)e.nextElement();
                File entryFile = new File(dir, entry.getName());
                // Ignore any manifest.mf entries.
                if (entry.getName().toLowerCase().endsWith("manifest.mf")) {
                    continue;
                }
                if (!entry.isDirectory()) {
                    entryFile.getParentFile().mkdirs();
                    FileOutputStream out = new FileOutputStream(entryFile);
                    InputStream zin = zipFile.getInputStream(entry);
                    byte[] b = new byte[512];
                    int len;
                    while ((len = zin.read(b)) != -1) {
                        out.write(b, 0, len);
                    }
                    out.flush();
                    out.close();
                    zin.close();
                }
            }
            zipFile.close();
        }
        catch (Throwable e) {
            e.printStackTrace();
        }
    }

    private void updateClasspath() {
        try {
            classLoader = new ModulClassLoader(getParentClassLoader(), MODUL_DIRECTORY);
        }
        catch (MalformedURLException e) {
            e.printStackTrace();
        }
        Thread.currentThread().setContextClassLoader(classLoader);
    }

        private ClassLoader getParentClassLoader() {
        ClassLoader parent = Thread.currentThread().getContextClassLoader();
        if (parent == null) {
            parent = this.getClass().getClassLoader();
            if (parent == null) {
                parent = ClassLoader.getSystemClassLoader();
            }
        }
        return parent;
    }

    public ClassLoader getPluginClassLoader() {
        return classLoader;
    }

    public void initializeModuls(){
        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                for(Modul modul : moduls){
                    long start = System.currentTimeMillis();
                    try{
                        modul.initialize();
                    }catch(Throwable e){
                        e.printStackTrace();
                    }

                    long end = System.currentTimeMillis();
                }
            }
        });


    }

    public void shutdownModuls(){
        for(Modul modul : moduls){
            try{
                modul.shutdown();
            }catch(Exception c){
                c.printStackTrace();
            }
        }
    }

        //check modul is install
        public boolean isInstalled(ModulPublic modul) {
        for (ModulPublic modulPublic : getModulPublics()) {
            if (modul.getName().equals(modulPublic.getName())) {
                return true;
            }
        }

        return false;
    }

    //expand all new modul 
    private void expandNewModuls(){
        File[] jars = MODUL_DIRECTORY.listFiles(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                boolean accept = false;
                String smallName = name.toLowerCase();
                if (smallName.endsWith(".jar")) {
                    accept = true;
                }
                return accept;
            }
        });

        // Do nothing if no jar or zip files were found
        if (jars == null) {
            return;
        }


        for (File jar : jars) {
            if (jar.isFile()) {

                URL url = null;
                try {
                    url = jar.toURI().toURL();
                }
                catch (MalformedURLException e) {
                    e.printStackTrace();
                }
                String name = URLFileSystem.getName(url);
                File directory = new File(MODUL_DIRECTORY, name);
                if (directory.exists() && directory.isDirectory()) {

                    // Check to see if directory contains the modul.xml file.
                    // If not, delete directory.
                    File modulXML = new File(directory, "modul.xml");
                    if (modulXML.exists()) {
                        if (modulXML.lastModified() < jar.lastModified()) {
                            uninstall(directory);
                            unzipModul(jar, directory);
                        }
                        continue;
                    }

                    uninstall(directory);
                } else {
                    // Unzip contents into directory
                    unzipModul(jar, directory);
                }
            }
        }
    }
}
